from twisted.internet import defer, reactor, protocol, threads
from twisted.python import failure
from twisted.protocols import basic
import time, sys, pprint, threading
from collections import deque
from twisted.python.threadpool import ThreadPool

def makeThread(self, *args, **kw):
    th = threading.Thread(*args, **kw)
    th.setDaemon(True)
    return th

ThreadPool.threadFactory = makeThread

class Timeout(Exception):
    pass

class RemoteClientProtocol(basic.LineOnlyReceiver):

    def __init__(self):
        self.running = False
        self.wait_answers = deque()

    def parse_pprint(self, rest):
        return pprint.pformat(rest.split())

    def parse_print(self, rest):
        return rest

    def parse_ping(self, rest):
        if not rest:
            return '[ping ok]'

    def parse_error(self, rest):
        return '[Error] '+rest

    def connectionMade(self):
        print 'Connection made'
        self.running = True
        reactor.callInThread(self.runCommandLine)
        self.autoPing()

    def autoPing(self):
        if not self.transport.disconnected:
            self.ask('ping auto')
            reactor.callLater(1, self.autoPing)

    def lineReceived(self, line):
        parts = line.split(None, 1)
        if len(parts) > 1:
            cmd, rest =  parts
        else:
            cmd, rest = parts[0], ''
        call = getattr(self, 'parse_'+cmd, None)
        if call is None:
            ret = '[Unknown answer type] '+str(cmd)
        else:
            ret = call(rest)
        d, delay = self.wait_answers.popleft()
        delay.cancel()
        d.callback(ret)

    def connectionLost(self, ex):
        print 'Connection lost', ex
        self.running = False
        if reactor.running:
            reactor.stop()

    def ask(self, line):
        d = defer.Deferred()
        delay = reactor.callLater(1, self.timeout, d)
        self.wait_answers.append((d, delay))
        self.sendLine(line)
        return d

    def timeout(self, d):
        d.errback(faulure.Failure(Timeout()))

    def runCommandLine(self):
        while self.running:
            sys.stdout.write('>>> ')
            cmd = raw_input()
            if not self.running:
                return
            if cmd == 'q':
                reactor.callInThread(self.transport.loseConnection)
                return
            try:
                print threads.blockingCallFromThread(reactor, self.ask, cmd)
            except Timeout:
                print 'Timeout'


class RemoteClientFactory(protocol.ClientFactory):
    protocol = RemoteClientProtocol

def run():
    reactor.connectTCP('localhost', 1234, RemoteClientFactory())
    reactor.run()

if __name__ == '__main__':
    run()
